import pygame
import time

from astral.scene import Registry
from astral.client import local
from astral.log import MessageLog

def log(*msg):
    return
    print(" ".join([str(x) for x in msg]))

class GameClient(object):
    def __init__(self):
        self.objects = Registry()
        self.control = None
        self.client_key = None
        self.owned = []
        self.transport = None
        self.lastack = time.time()
        self.running = True
        
        self.update_rate = .033
        self.last_update = None
        self.interpolation_rate = .002
        self.last_interpolation = None
        self.next_update = self.update_rate
        self.updates = {}
        self.keep_updates = 10
        
        self.update_count = None  #Starts in sync with server and then gets ahead
        self.server_update_count = None #Last update count we received from the server
        self.rate_skew = 2   #How fast to increment the update counter
        self.max_count_skew = 10  #How far different the update rate and server update rate before we adjust skew
        
        self.input_buffer = {}   #List of inputs to send out
        self.input_rate = .033  #Should match server update rate
        self.next_input = self.input_rate
        
        self.message_log = MessageLog()
    def connect(self,host,port,library="geometrian"):
        if self.transport:
            return
        if library=="geometrian":
            from astral.client.client_adapters import MastermindTCPAdapter
            self.transport = MastermindTCPAdapter(self,host,port)
        elif library=="podsixnet":
            from astral.client.client_adapters import PodSixTCPAdapter
            self.transport = PodSixTCPAdapter(self,host,port)
    def handle_data(self,data):
        log(data)
        if type(data)!=type({}):
            return
        msg_type = data.get("action","")
        del data["action"]
        f = getattr(self,"msg_"+msg_type,None)
        if not f:
            log("Unhandled message type",msg_type)
            return
        f(**data)
    def disconnect(self):
        """How to initiate a disconnection from the server"""
        self.transport.close()
        self.transport = None
    def msg_disconnected(self):
        """What to do when the server disconnects us"""
        self.disconnect()
    def listen(self):
        if self.transport:
            self.transport.listen()
            
        if not self.last_update:
            self.last_update = time.time()
        dt = (time.time()-self.last_update)
        self.last_update = time.time()
        
        if not self.update_count:
            return

        self.tick()
        
        if not self.last_interpolation:
            self.last_interpolation = time.time()
        if time.time()-self.last_interpolation>self.interpolation_rate:
            self.interpolate_objects()
            self.last_interpolation = time.time()
        
        self.next_update -= dt*self.rate_skew
        if self.next_update <= 0:
            self.next_update = self.update_rate
            self.update_count += 1
            self.input_buffer[self.update_count] = []
            #print self.update_count-self.server_update_count
            self.update_objects()
        if self.update_count-self.server_update_count>self.max_count_skew:
            self.rate_skew = 0.75
        elif self.server_update_count-self.update_count<self.max_count_skew:
            self.rate_skew *= 1.25
            
        self.next_input -= dt
        if self.next_input <= 0:
            self.next_input = self.input_rate
            self.send_input()
    def buffer_action(self,action):
        """Add an action to the current tick actions, to be sent to server in
        set increments"""
        current_input = self.input_buffer[self.update_count]
        if action not in current_input:
            current_input.append(action)
        self.input_buffer[self.update_count] = current_input
    def send_input(self):
        keys = self.input_buffer.keys()
        keys.sort()
        for k in keys:
            if ".p" in self.input_buffer[k]:
                self.send({"action":"input","data":self.input_buffer[k],"update_count":k})
                del self.input_buffer[k]
    def msg_ping(self):
        self.send({"action":"pong"})
    def send(self,data):
        if self.transport:
            self.transport.send(data)
    def announce(self):
        self.send({"action":"authenticate","data":{}})
    def msg_serverfull(self,value):
        """The server has too many players. Client should inform player somehow"""
        log("server is full")
    def logoff(self):
        self.send({"action":"logoff","value":self.client_key})
    def msg_handshake(self,key,server_state_rate):
        self.client_key = key
        self.server_state_rate = server_state_rate
        log("assign key",self.client_key,"to",self)
    def msg_player_owns(self,keys):
        self.owned = keys
        print self.owned
    def msg_authenticated(self,value):
        log(value)
    def msg_worldupdate(self,states,update_count):
        """The server wants us to update our object positions"""
        self.server_update_count = update_count
        if not self.update_count:
            self.update_count = update_count
            self.input_buffer[self.update_count] = []
        self.updates[update_count] = states
        if len(self.updates)>self.keep_updates:
            keys = self.updates.keys()
            keys.sort()
            del self.updates[keys[0]]
        
        for key in states:
            state = states[key]
            state["_update_count"] = update_count
            if key not in self.objects:
                o = self.objects[key] = self.create_local(key,state)
                state["_force_apply"] = True   #Ensure full state is applied on creation
            o = self.objects[key]
            o.buffer_state(state)
        for key in self.objects.keys():
            if key not in states:
                del self.objects[key]
    def create_local(self,key,state):
        """Create a local instance of a remote object.
        Use the values in the state as a guide for how it should be created.
        Assign its key.
        By default, will pick a class from client.local matching the template"""
        cls = getattr(local,state.get("template","RemoteObject"))
        return cls(key)
    def update_objects(self):
        """Run prediction or other updates on objects"""
        for k in self.objects:
            if not k in self.owned:
                continue
            o = self.objects[k]
            if self.update_count-1 in self.input_buffer:
                d = self.input_buffer[self.update_count-1]
                #Only predict states we haven't predicted yet
                if ".p" in d:
                    continue
                o.prediction(o.last_state,d,self.update_count-1)
                o.correct_prediction(self.update_count-1)
                d.append(".p")
    def interpolate_objects(self):
        """Increase every objects .t value and apply states"""
        for o in self.objects.all():
            o.increment_time(self.interpolation_rate,self.server_state_rate)
    def tick(self):
        """Something that happens every frame"""
    def input(self):
        """Overwrite for actual input"""
    def msg_message(self,data):
        """Incoming message for the log"""
        self.message_log.log(**data)